package top.abrsm.controller;

import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayConstants;
import com.github.wxpay.sdk.WXPayUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import top.abrsm.config.PushMessage;
import top.abrsm.entity.*;
import top.abrsm.service.IMessageService;
import top.abrsm.service.IOrderService;
import top.abrsm.service.IProductService;
import top.abrsm.service.IRefundService;
import top.abrsm.util.*;

import javax.annotation.Resource;
import javax.net.ssl.SSLContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.*;
import java.security.KeyStore;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Controller
@RequestMapping ( "/pay" )
@Slf4j
public class PayController {

    @Resource
    private IOrderService iOrderService;
    @Resource
    private IMessageService iMessageService;
    @Resource
    private IRefundService iRefundService;
    @Resource
    private IProductService iProductService;
    @Resource
    private PushMessage pushMessage;
    @Value ( "${keyPath}" )
    private String keyPath;

    /**
     * 微信支付
     *
     * @param outTradeNo：商户订单号
     * @param actualMoney：实际交易价格，扩大100倍送给微信的价格
     * @param totalAmount：实际交易原价格
     * @param productId：商品Id
     * @param session
     * @return
     */
    @RequestMapping ( "/wxPay" )
    @ResponseBody
    public Map<String, String> wxPay ( String outTradeNo, String actualMoney, String totalAmount, String productId, HttpSession session ) {
        Map<String, String> map = new HashMap<String, String> ();
        //生成【统一下单API】所需参数的接口
        Map<String, String> payMap = new HashMap<String, String> ();
        payMap.put ( "appid", ConstUtil.PAY_AppID );//公众账号ID
        payMap.put ( "mch_id", ConstUtil.MCH_ID );//商户号
        payMap.put ( "body", "TOP英皇乐理-在线模考" );//商品描述
        String nonce_str = UUID.randomUUID ().toString ().trim ().replaceAll ( "-", "" );
        payMap.put ( "nonce_str", nonce_str );
        payMap.put ( "notify_url", ConstUtil.NOTIFY_URL );//通知地址
        payMap.put ( "out_trade_no", outTradeNo );//商户订单号，商户系统产生的随机数
        payMap.put ( "spbill_create_ip", "127.0.0.1" );//终端ip
        payMap.put ( "trade_type", "NATIVE" );//交易类型
        payMap.put ( "total_fee", actualMoney );//实际总金额
        String sign = PayUtil.createSign ( payMap, ConstUtil.KEY );//调用生成签名的方法，用以Map集合中的相关参数生成签名
        payMap.put ( "sign", sign );//签名
        //将订单对象转为xml格式
        String orderInfo = "";
        try {
            orderInfo = WXPayUtil.mapToXml ( payMap );//maptoXml方法是微信sdk自带的方法
        } catch ( Exception e ) {
            log.error ( "微信下单生成支付二维码失败 -> [{}]", e );
        }
        String code_url = PayUtil.httpOrder ( orderInfo );//调用统一下单接口
        map.put ( "url", code_url );
        OrderEntity orderEntity = new OrderEntity ();
        orderEntity.setProductId ( productId );
        StudentEntity studentEntity = ( StudentEntity ) session.getAttribute ( "studentEntity" );
        orderEntity.setStuId ( studentEntity.getId () );
        orderEntity.setOutTradeNo ( outTradeNo );//商户订单号，跳转到订单页商户随机生成的
        orderEntity.setActualPrice ( totalAmount ); // 实际价格
        RedisUtils.set ( outTradeNo, orderEntity, 600L, TimeUnit.SECONDS ); // 将该订单存入Redis中，异步通知去取
        return map;
    }


    /**
     * 2.微信支付异步通知，由微信服务器发出
     *
     * @param request
     * @return
     */
    @RequestMapping ( value = "/payNotice/orders/notice", method = RequestMethod.POST )
    @ResponseBody
    public String wxNotice ( HttpServletRequest request ) {
        log.info ( "微信支付异步通知" );
        WXPay wxPay = new WXPay ( new ConstUtil () );
        try {
            ByteArrayOutputStream outSteam = new ByteArrayOutputStream ();
            Map<String, String> notifyMap = new HashMap<> ();
            try {
                InputStream inStream = request.getInputStream ();
                byte[] buffer = new byte[1024];
                int len = 0;
                while ( ( len = inStream.read ( buffer ) ) != - 1 ) {
                    outSteam.write ( buffer, 0, len );
                }
                String result = new String ( outSteam.toByteArray (), "utf-8" );
                // 关闭流
                outSteam.close ();
                inStream.close ();
                notifyMap = WXPayUtil.xmlToMap ( result );  // 转换成map
            } catch ( Exception e ) {
                e.printStackTrace ();
            }
            if ( wxPay.isPayResultNotifySignatureValid ( notifyMap ) ) {
                String total_fee = notifyMap.get ( "total_fee" );
                String result_code = notifyMap.get ( "result_code" );
                String return_code = notifyMap.get ( "return_code" );
                String out_trade_no = notifyMap.get ( "out_trade_no" );
                // 下单时存入session时的订单编号
                OrderEntity orderEntity = new OrderEntity ();
                orderEntity.setOutTradeNo ( out_trade_no );
                orderEntity = iOrderService.querySingle ( orderEntity );
                log.info ( "异步通知和微信返回信息 -> [订单信息：{}，总价：{}, 结果码：{}, 返回码：{}, 订单号：{}]", orderEntity, total_fee, result_code, return_code, out_trade_no );
                if ( orderEntity != null && "SUCCESS".equals ( return_code ) && "SUCCESS".equals ( result_code ) ) {
                    orderEntity.setTradeStatus ( 2 );
                    orderEntity.setBuyerLoginId ( notifyMap.get ( "openid" ) );
                    iOrderService.updateSingle ( orderEntity );
                    log.info ( "新增置顶贴吧订单->[订单Id：{}，商户订单号：{}，状态：{}]", orderEntity.getId (), orderEntity.getOutTradeNo (), orderEntity.getTradeStatus () );
                    //2. 新增消息
                    MessageEntity messageEntity = new MessageEntity ( SnowflakeIdWorker.getInstance ().nextStringId (), orderEntity.getProductId (), orderEntity.getStuId (), orderEntity.getId (), "您已微信支付贴吧置顶订单，订单号为：【" + orderEntity.getId () + "】。" + ( Integer.parseInt ( total_fee ) / 100 ) + "元", 0 );
                    iMessageService.saveSingle ( messageEntity );
                    // 可以删除Redis中的订单信息
                    log.info ( "微信支付成功并保持数据库" );
                    return "SUCCESS";
                } else {
                    log.error ( "微信异步保存数据库失败..." );
                    return "FAIL";
                }
            } else {
                // 签名失败, 写日志
                log.error ( "微信签名失败 -> [{}]", notifyMap );
                return "FAIL";
            }
        } catch ( Exception e ) {
            e.printStackTrace ();
            return "FAIL";
        }
    }


    /**
     * 3.订单查询，前端生成支付二维码之后每隔3秒就请求该方法进行验证支付是否成功
     * 支付成功后停止轮询，关闭二维码后也停止轮询
     *
     * @throws Exception
     */
    @RequestMapping ( "queryWxOrder" )
    @ResponseBody
    public ResultBody orderQuery ( String outTradeNo ) {
        HashMap<String, String> data = new HashMap<String, String> ();
        data.put ( "out_trade_no", outTradeNo );
        try {
            WXPay wxPay = new WXPay ( new ConstUtil () );
            Map<String, String> resp = wxPay.orderQuery ( data );
            if ( resp.get ( "result_code" ).equals ( "SUCCESS" ) && resp.get ( "return_code" ).equals ( "SUCCESS" ) && resp.get ( "trade_state" ).equals ( "SUCCESS" ) ) {
                // 下单时存入session时的订单编号
                OrderEntity orderEntity = new OrderEntity ();
                orderEntity.setOutTradeNo ( outTradeNo );
                orderEntity = iOrderService.querySingle ( orderEntity );
                if ( orderEntity != null ) {
                    orderEntity.setTradeStatus ( 2 );
                    orderEntity.setBuyerLoginId ( resp.get ( "openid" ) );
                    iOrderService.updateSingle ( orderEntity );
                    // 公众号消息推送
                    pushMessage.sendBuy ( orderEntity );
                    // 更新库存
                    updateStork ( orderEntity.getProductId (), 1 );
                    log.info ( "采购成功，库存-1" );
                }
                log.info ( "轮询微信支付成功" );
                return ResultBody.success ();
            }
            return ResultBody.fail ( StateEnum.PAY_FAIL ); // 支付失败
        } catch ( Exception e ) {
            return ResultBody.fail ( StateEnum.PAY_FAIL ); // 支付失败
        }
    }


    /**
     * 退款
     */
    @RequestMapping ( "refund" )
    @ResponseBody
    public ResultBody refund ( OrderEntity orderEntity ) {
        orderEntity = iOrderService.querySingle ( orderEntity );
        try {
            Map<String, String> reqData = new HashMap<> ();
            reqData.put ( "appid", ConstUtil.WX_App_ID );
            reqData.put ( "mch_id", ConstUtil.MCH_ID );
            reqData.put ( "nonce_str", WXPayUtil.generateNonceStr () );//随机字符串
            reqData.put ( "sign_type", "MD5" );
            reqData.put ( "out_trade_no", orderEntity.getOutTradeNo () );//付款微信订单号
            reqData.put ( "out_refund_no", UUID.randomUUID ().toString () );//退款订单号
            reqData.put ( "total_fee", String.valueOf ( ( int ) ( Double.parseDouble ( orderEntity.getActualPrice () ) * 100 ) ) );//订单总金额，单位为分
            reqData.put ( "refund_fee", String.valueOf ( ( int ) ( Double.parseDouble ( orderEntity.getActualPrice () ) * 100 ) ) );//退款总金额，单位为分
            reqData.put ( "sign", WXPayUtil.generateSignature ( reqData, ConstUtil.KEY, WXPayConstants.SignType.MD5 ) );
            //微信统一下单
            // 将所有参数转换为xml形式
            String xmlParam = WXPayUtil.mapToXml ( reqData );
            String xmlStr = doRefund ( ConstUtil.REFUND_URL, xmlParam );
            Map<String, String> map = WXPayUtil.xmlToMap ( xmlStr );
            System.out.println ( map );
            if ( map == null || ! "SUCCESS".equals ( map.get ( "return_code" ) ) ) {
                //消息通知
                System.out.println ( "-----退款发起失败-----" + map );
                return null;
            } else {
                // 退款成功，修改订单状态，添加退款记录
                OrderEntity order = new OrderEntity ();
                order.setId ( orderEntity.getId () );
                order.setTradeStatus ( ConstUtil.ORDER_STATUS_REFUND );
                order = iOrderService.updateSingle ( order );
                RefundEntity refundEntity = new RefundEntity ();
                refundEntity.setOrderId ( order.getId () );
                refundEntity.setRefundMoney ( orderEntity.getActualPrice () );
                refundEntity.setRefundDesc ( "主动申请退款" );
                refundEntity.setStuId ( orderEntity.getStuId () );
                refundEntity = iRefundService.saveSingle ( refundEntity );
                updateStork ( orderEntity.getProductId (), 0 );
                log.info ( "退货成功，库存+1" );
            }
        } catch ( Exception e ) {
            e.printStackTrace ();
        }
        return ResultBody.success ( orderEntity );
    }


    /**
     * 申请退款
     */
    private String doRefund ( String url, String data ) {
        StringBuilder sb = new StringBuilder ();
        try {
            KeyStore keyStore = KeyStore.getInstance ( "PKCS12" );
            //证书
            FileInputStream instream = new FileInputStream ( new File ( keyPath ) );
            String mchid = ConstUtil.MCH_ID;
            try {
                keyStore.load ( instream, mchid.toCharArray () );
            } finally {
                instream.close ();
            }
            // 证书
            SSLContext sslcontext = SSLContexts.custom ().loadKeyMaterial ( keyStore, mchid.toCharArray () ).build ();
            // 只允许TLSv1协议
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory ( sslcontext, new String[]{"TLSv1"}, null, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER );
            //创建基于证书的httpClient,后面要用到
            CloseableHttpClient client = HttpClients.custom ().setSSLSocketFactory ( sslsf ).build ();
            HttpPost httpPost = new HttpPost ( url );//退款接口
            StringEntity reqEntity = new StringEntity ( data );
            // 设置类型
            reqEntity.setContentType ( "application/x-www-form-urlencoded" );
            httpPost.setEntity ( reqEntity );
            CloseableHttpResponse response = client.execute ( httpPost );
            try {
                HttpEntity entity = response.getEntity ();
                if ( entity != null ) {
                    BufferedReader bufferedReader = new BufferedReader ( new InputStreamReader ( entity.getContent (), "UTF-8" ) );
                    String text = "";
                    while ( ( text = bufferedReader.readLine () ) != null ) {
                        sb.append ( text );
                    }
                }
                EntityUtils.consume ( entity );
            } catch ( Exception e ) {
                e.printStackTrace ();
            } finally {
                try {
                    response.close ();
                } catch ( IOException e ) {
                    e.printStackTrace ();
                }
            }
        } catch ( Exception e ) {
            e.printStackTrace ();
        }
        return sb.toString ();
    }

    /**
     * 更新库存
     *
     * @param id
     * @param type
     */
    private void updateStork ( String id, int type ) {
        ProductEntity productEntity = new ProductEntity ();
        productEntity.setId ( id );
        productEntity = iProductService.querySingle ( productEntity );
        int stockNumber = productEntity.getStockNumber ();
        int i = type == 1 ? ( stockNumber - 1 ) : ( stockNumber + 1 );
        ProductEntity product = new ProductEntity ();
        product.setId ( id );
        product.setStockNumber ( i );
        product = iProductService.updateSingle ( product );
    }


}
